package com.thot.timyag.core;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.thot.timyag.common.CommonUtil;
import com.thot.timyag.config.properties.ThotMyBatisProperties;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;

import static org.springframework.util.ObjectUtils.isEmpty;

/**
 * @author yangjf
 * @time 2019-05-13 10:48
 */

public class MapperHotDeployPlugin {

    private final static Logger logger = LoggerFactory.getLogger(MapperHotDeployPlugin.class);

    private ThotMyBatisProperties myBatisProperties;

    private ClassLoader classLoader;

    private List<String> registeredMapperBeans = new ArrayList<>();

    private volatile Configuration configuration;

    public MapperHotDeployPlugin(Configuration configuration, ThotMyBatisProperties myBatisProperties, ClassLoader classLoader) {
        this.configuration = configuration;
        this.myBatisProperties = myBatisProperties;
        this.classLoader = classLoader;
    }

    public void setClassLoader(ClassLoader cl) {
        this.classLoader = cl;
    }


    public void removeMapperBeans() {
        if (!CollectionUtils.isEmpty(registeredMapperBeans)) {
            for (String name : registeredMapperBeans) {
                ApplicationContextProvider.removeBean(name);
            }
            registeredMapperBeans.clear();
        }
    }

    public void reloadMapperBean() {
        this.registeredMapperBeans = ApplicationContextProvider.scanMapperBean(this.classLoader, myBatisProperties.getBasePackages());
        System.out.println("loaded mapper : " + registeredMapperBeans);
    }

    public void reloadXml() throws NestedIOException {
        if (this.classLoader == null) {
            throw new RuntimeException();
        }
        Collection<Class<?>> oldRegistry = configuration.getMapperRegistry().getMappers();
        List<String> oldRegistryAry = oldRegistry.stream().map(Class::getName).collect(Collectors.toList());
        Resource[] resources = myBatisProperties.resolveMapperLocations(this.classLoader);

        if (!isEmpty(resources)) {
            for (Resource mapperLocation : resources) {
                if (mapperLocation == null) {
                    continue;
                }
                try {
                    clearMap(getNamespace(mapperLocation));
                    clearSet(mapperLocation.toString());
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    Resources.setDefaultClassLoader(classLoader);
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }
            }
            Collection<Class<?>> newRegistry = configuration.getMapperRegistry().getMappers();
            List<String> newRegistryAry = newRegistry.stream().map(Class::getName).collect(Collectors.toList());
            newRegistryAry.removeAll(oldRegistryAry);
        }
    }

    /**
     * 清除文件记录缓存
     *
     * @param resource xml文件路径
     * @date ：2018/12/19
     * @author ：zc.ding@foxmail.com
     */
    private void clearSet(String resource) {
        logger.info("清理mybatis的资源{}在容器中的缓存", resource);
        Object value = CommonUtil.getFieldValue(configuration, "loadedResources");
        if (value instanceof Set) {
            Set<?> set = (Set) value;
            set.remove(resource);
            set.remove("namespace:" + resource);
        }
    }


    /**
     * 删除xml元素的节点缓存
     *
     * @param nameSpace xml中命名空间
     * @date ：2018/12/19
     * @author ：zc.ding@foxmail.com
     */
    private void clearMap(String nameSpace) {
        logger.info("清理Mybatis的namespace={}在mappedStatements、caches、resultMaps、parameterMaps、keyGenerators、sqlFragments中的缓存");
        Arrays.asList("mappedStatements", "caches", "resultMaps", "parameterMaps", "keyGenerators", "sqlFragments").forEach(fieldName -> {
            Object value = CommonUtil.getFieldValue(configuration, fieldName);
            if (value instanceof Map) {
                Map<?, ?> map = (Map) value;
                List<Object> list = map.keySet().stream().filter(o -> o.toString().startsWith(nameSpace + ".")).collect(Collectors.toList());
                logger.info("需要清理的元素: {}", list);
                list.forEach(k -> map.remove((Object) k));
            }
        });
    }

    /**
     * 获取xml的namespace
     *
     * @param resource xml资源
     * @return java.lang.String
     * @date ：2018/12/19
     * @author ：zc.ding@foxmail.com
     */
    private String getNamespace(Resource resource) {
        logger.info("从{}获取namespace", resource.toString());
        try {
            XPathParser parser = new XPathParser(resource.getInputStream(), true, null, new XMLMapperEntityResolver());
            return parser.evalNode("/mapper").getStringAttribute("namespace");
        } catch (Exception e) {
            logger.info("ERROR: 解析xml中namespace失败", e);
            throw new RuntimeException("ERROR: 解析xml中namespace失败", e);
        }
    }


}
